package com.magenta.maxoptra.geocoding.decarta;

import com.magenta.maxoptra.gis.commons.Logger;
import com.magenta.maxoptra.geocoding.decarta.jaxb.*;
import com.magenta.maxoptra.gis.GeoException;
import com.magenta.maxoptra.gis.GeoRoute;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import javax.xml.bind.*;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;

/**
 * Created by const Date: 25.03.2009 Time: 16:54:39
 * <p/>
 * The utility class to locate any common routines for the decarta WS requests.
 * Uses decarta's dds-ws-4.3.4 api.
 * <p/>
 */
public abstract class DecartaWSRequest<T> {
    private static final Logger LOG = new Logger(DecartaWSRequest.class);

    final ObjectFactory factory = new ObjectFactory();
    final DataSource dataSource;
    private final Marshaller marshaller;
    private final Unmarshaller unmarshaller;
    private final HttpClient client;

    DecartaWSRequest(HttpClient client) {
        this.client = client;
        dataSource = newDataSource();
        marshaller = newMarshaller();
        unmarshaller = newUnmarshaller();
    }

    protected abstract T execute() throws GeoException;

    GeoRoute handleErrors(ResponseType response, Class clazz) throws GeoException {
        ErrorListType errors = response.getErrorList();
        if (errors != null && errors.getError().size() > 0) {
            StringBuffer message = new StringBuffer("");
            for (ErrorType error : response.getErrorList().getError()) {
                message.append(error.getMessage())
                        .append("[code:")
                        .append(error.getErrorCode())
                        .append("];\n\r");
            }
            if (errors.getHighestSeverity().equals(SeverityType.ERROR)) {
                throw new GeoException("Decarta " + clazz.getSimpleName() + " error: " + message);
            } else {
                LOG.warn("Decarta " + clazz.getSimpleName() + " warning: " + message);
            }
        } else {
            throw new InvalidUserKeyException();
        }
        return null;
    }


    AddressType createFreeFormAddress(String freeformAddr, String region, String language) {
        AddressType addr = factory.createAddressType();
        addr.setFreeFormAddress(freeformAddr);
        addr.setCountryCode(region);
        addr.setLanguage(language.toUpperCase());
        return addr;
    }

    JAXBElement<PositionType> createPos(Double lat, Double lon) {
        DirectPositionType pos = factory.createDirectPositionType();
        pos.getValue().add(lat);
        pos.getValue().add(lon);
        PointType point = factory.createPointType();
        point.setPos(pos);
        PositionType position = factory.createPositionType();
        position.setPoint(point);
        return factory.createPosition(position);
    }

    DataSource newDataSource() {
        return new DataSource();
    }

    /*
        use this marshaller to translate the JAXB objects to xml
     */
    private Marshaller newMarshaller() {
        try {
            JAXBContext jc = JAXBContext.newInstance("com.magenta.maxoptra.geocoding.decarta.jaxb");
            Marshaller marshaller = jc.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            return marshaller;
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
    }

    private Unmarshaller newUnmarshaller() {
        try {
            JAXBContext jc = JAXBContext.newInstance("com.magenta.maxoptra.geocoding.decarta.jaxb");
            return jc.createUnmarshaller();
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
    }

    /*
        the authentication credentials are set here
     */
    private JAXBElement<RequestHeaderType> newHeader() {
        RequestHeaderType header = factory.createRequestHeaderType();
        header.setClientName(System.getProperties().getProperty("clientName", "magenta-kp"));
        header.setClientPassword(System.getProperties().getProperty("clientPassword", "rs9s54"));
        header.setConfiguration(System.getProperties().getProperty("serviceConfiguration", "stickville"));
        header.setSessionID("999");
        return factory.createRequestHeader(header);
    }

    XLSMessage newXLSMessage(JAXBElement<? extends AbstractRequestParametersType> params, String methodName) {
        RequestType request = factory.createRequestType();
        request.setRequestParameters(params);
        request.setMethodName(methodName);
        request.setMaximumResponses(new java.math.BigInteger("25"));
        request.setRequestID("10");
        request.setVersion("1.0");
        XLSType msg = factory.createXLSType();
        msg.getBody().add(factory.createRequest(request));
        msg.setVersion(new java.math.BigDecimal("1.0"));
        msg.setLang("en");
        msg.setHeader(newHeader());
        return factory.createXLSMessage(msg);
    }

    class DataSource {
        public XLSMessage executeRequest(final XLSMessage msg) throws GeoException {
//            HttpURLConnection c = null;
            try {
                URL url = new URL(System.getProperty("decartaWSUrl", "http://wsdds2.dz.decarta.com/openls/openls"));
                HttpPost post = new HttpPost(url.toURI());

                StringWriter writer = new StringWriter();
                marshaller.marshal(msg, writer);
                writer.flush();
                StringEntity reqEntity = new StringEntity(writer.toString(),
                        HTTP.UTF_8);
                reqEntity.setContentType("text/xml");
                reqEntity.setContentEncoding(HTTP.UTF_8);
                System.out.println(reqEntity.getContentLength());
                reqEntity.setChunked(false);

                post.setEntity(reqEntity);

                HttpResponse response = client.execute(post);

//                c = (HttpURLConnection) url.openConnection();
//                c.setDoOutput(true);
//                c.setDoInput(true);
//                c.setRequestMethod("POST");
//
//                OutputStream out = c.getOutputStream();

                LOG.info("Decarta geocoding request:");
                marshaller.marshal(msg, System.out);

                if (!"text/xml".equals(response.getEntity().getContentType().getValue())) {
                    throw new GeoException("Response unparseable!");
                }

                //String encoding = response.getEntity().getContentEncoding().getValue();
                StringReader reader = new StringReader(EntityUtils.toString(response.getEntity()));
//                InputStream inputStream = c.getInputStream();
                XLSMessage result = (XLSMessage) unmarshaller.unmarshal(reader);
                LOG.info("Decarta geocoding response:");
                marshaller.marshal(result, System.out);
                return result;
            } catch (Exception e) {
                throw new GeoException(e);
            } finally {
//                if (c != null) {
//                    c.disconnect();
//                }
            }
        }
    }
}
